{
  // 消除异步的传染性

  // async function getUser() {
  //   return await fetch('./test.json').then(resp => resp.json())
  // }

  // async function m1() {
  //   return await getUser();
  // }

  // async function m2() {
  //   return await m1();
  // }

  // async function m3() {
  //   return await m2();
  // }

  // async function main() {
  //   const user = await m3();
  //   console.log('user: ', user);
  // }

  // main();

  // 函数getUser是通过异步方式封装的请求，其他方法如果想要这个方法的返回结果，必须要用异步的方式获取；
  // 一个节点发生异步，这个节点的所有调用链全部需要异步，这就是异步的传染性

  // 思路：引发函数报错 执行函数两次，通过函数缓存数据拿到最终函数的结果数据
  // 第一次执行函数：
  // 函数开始 -> fetch -> 引发报错 -> 函数结束
  //            fetch引发报错的同时网络请求在后台默默执行fetch -> 拿到fetch执行的结果data -> 缓存data
  // 第二次执行函数：
  // 函数开始 -> fetch -> 交付data -> 函数继续 -> 函数结束
  // react的suspense组件就是用的这个思想

  function getUser() {
    return window.fetch('./test.json');
  }

  function m1() {
    return getUser();
  }

  function m2() {
    return m1();
  }

  function m3() {
    return m2();
  }

  function main() {
    const user = m3();
    console.log('user: ', user);
  }

  function run(func) {
    let cache = []; // 缓存结果 防止函数多次调用所以用数组表示缓存结果
    let i = 0; // 请求索引

    const originalFetch = window.fetch; // 保存fetch方法

    // 重写fetch
    window.fetch = (...args) => {
      console.log('输出次数');

      // 有缓存 交付缓存结果
      if (cache[i]) {
        if (cache[i].status === 'fulfilled') {
          return cache[i].data;
        } else if (cache[i].status === 'rejected') {
          throw cache[i].err;
        }
      }

      const result = {
        status: 'pending',
        data: null,
        err: null,
      };
      cache[i++] = result; // 发送请求

      const p = originalFetch(...args)
        .then((resp) => resp.json())
        .then(
          (res) => {
            result.status = 'fulfilled';
            result.data = res;
          },
          (err) => {
            result.status = 'rejected';
            result.err = err;
          }
        );

      // 马上报错，让函数强制执行完毕变成同步函数，不用等异步回来才往下执行
      // 把原生fetch的promise作为错误直接抛出去，让fetch马上结束
      throw p;
    };

    // 捕获错误
    // 劫持原生fetch函数报错后，就会try-catch住
    try {
      // 运行传入的函数
      func();
    } catch (err) {
      // 什么时候引发重新执行func?(捕获promise错误即可)
      if (err instanceof Promise) {
        // 重新发起请求 并将i置零
        const reRun = () => {
          i = 0;
          func();
        };

        // 异步请求成功或者失败都重新执行原来的函数
        // 注意此处并非递归，只是不断从微任务队列获取任务执行，所以不会栈溢出。但是有可能同一事件循环太多任务，导致页面卡顿
        err.then(reRun, reRun);
      }
    }
  }

  run(main);
}
